import Foundation
import UIKit
import AVFoundation
import Display
import TelegramCore
import SwiftSignalKit
import Photos
import CoreLocation
import Contacts
import UserNotifications
import CoreTelephony
import TelegramPresentationData
import AccountContext

public enum DeviceAccessCameraSubject {
    case video
    case videoCall
    case qrCode
    case ageVerification
}

public enum DeviceAccessMicrophoneSubject {
    case audio
    case video
    case voiceCall
}

public enum DeviceAccessMediaLibrarySubject {
    case send
    case save
    case wallpaper
    case qrCode
}

public enum DeviceAccessLocationSubject {
    case send
    case live
    case tracking
    case weather
}

public enum DeviceAccessSubject {
    case camera(DeviceAccessCameraSubject)
    case microphone(DeviceAccessMicrophoneSubject)
    case mediaLibrary(DeviceAccessMediaLibrarySubject)
    case location(DeviceAccessLocationSubject)
    case contacts
    case notifications
    case siri
    case cellularData
}

private let cachedMediaLibraryAccessStatus = Atomic<Bool?>(value: nil)

public func shouldDisplayNotificationsPermissionWarning(status: AccessType, suppressed: Bool) -> Bool {
    switch (status, suppressed) {
        case (.allowed, _), (.unreachable, true), (.notDetermined, true):
            return false
        default:
            return true
    }
}

public final class DeviceAccess {
    private static let contactsPromise = Promise<Bool?>(nil)
    static var contacts: Signal<Bool?, NoError> {
        return self.contactsPromise.get()
        |> distinctUntilChanged
    }
    
    private static let notificationsPromise = Promise<Bool?>(nil)
    static var notifications: Signal<Bool?, NoError> {
        return self.notificationsPromise.get()
    }
    
    private static let siriPromise = Promise<Bool?>(nil)
    static var siri: Signal<Bool?, NoError> {
        return self.siriPromise.get()
    }
    
    private static let locationPromise = Promise<Bool?>(nil)
    static var location: Signal<Bool?, NoError> {
        return self.locationPromise.get()
    }
    
    private static let cameraPromise = Promise<Bool?>(nil)
    static var camera: Signal<Bool?, NoError> {
        return self.cameraPromise.get()
    }
    
    private static let microphonePromise = Promise<Bool?>(nil)
    static var microphone: Signal<Bool?, NoError> {
        return self.microphonePromise.get()
    }
    
    public static func isMicrophoneAccessAuthorized() -> Bool? {
        return AVAudioSession.sharedInstance().recordPermission == .granted
    }
    
    public static func isCameraAccessAuthorized() -> Bool {
        return AVCaptureDevice.authorizationStatus(for: .video) == .authorized
    }
        
    public static func authorizationStatus(applicationInForeground: Signal<Bool, NoError>? = nil, siriAuthorization: (() -> AccessType)? = nil, subject: DeviceAccessSubject) -> Signal<AccessType, NoError> {
        switch subject {
            case .notifications:
                let status = (Signal<AccessType, NoError> { subscriber in
                    if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
                        UNUserNotificationCenter.current().getNotificationSettings(completionHandler: { settings in
                            switch settings.authorizationStatus {
                                case .authorized:
                                    if settings.alertSetting == .disabled {
                                        subscriber.putNext(.unreachable)
                                    } else {
                                        subscriber.putNext(.allowed)
                                    }
                                case .denied:
                                    subscriber.putNext(.denied)
                                case .notDetermined:
                                    subscriber.putNext(.notDetermined)
                                default:
                                    subscriber.putNext(.notDetermined)
                            }
                            subscriber.putCompletion()
                        })
                    } else {
                        subscriber.putNext(.notDetermined)
                        subscriber.putCompletion()
                    }
                    return EmptyDisposable
                } |> afterNext { status in
                    switch status {
                        case .allowed, .unreachable:
                            DeviceAccess.notificationsPromise.set(.single(nil))
                        default:
                            break
                    }
                } )
                |> then(self.notifications
                    |> mapToSignal { authorized -> Signal<AccessType, NoError> in
                        if let authorized = authorized {
                            return .single(authorized ? .allowed : .denied)
                        } else {
                            return .complete()
                        }
                    })
                if let applicationInForeground = applicationInForeground {
                    return applicationInForeground
                    |> distinctUntilChanged
                    |> mapToSignal { inForeground -> Signal<AccessType, NoError> in
                        return status
                    }
                } else {
                    return status
                }
            case .contacts:
                let status = Signal<AccessType, NoError> { subscriber in
                    switch CNContactStore.authorizationStatus(for: .contacts) {
                        case .notDetermined:
                            subscriber.putNext(.notDetermined)
                        case .authorized:
                            subscriber.putNext(.allowed)
                        case .limited:
                            subscriber.putNext(.limited)
                        default:
                            subscriber.putNext(.denied)
                    }
                    subscriber.putCompletion()
                    return EmptyDisposable
                }
                return status
                |> then(self.contacts
                    |> mapToSignal { authorized -> Signal<AccessType, NoError> in
                        if let authorized {
                            return .single(authorized ? .allowed : .denied)
                        } else {
                            return .complete()
                        }
                    })
            case .cellularData:
                return Signal { subscriber in
                    if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
                        func statusForCellularState(_ state: CTCellularDataRestrictedState) -> AccessType? {
                            switch state {
                                case .restricted:
                                    return .denied
                                case .notRestricted:
                                    return .allowed
                                default:
                                    return .allowed
                            }
                        }
                        let cellState = CTCellularData.init()
                        if let status = statusForCellularState(cellState.restrictedState) {
                            subscriber.putNext(status)
                        }
                        cellState.cellularDataRestrictionDidUpdateNotifier = { restrictedState in
                            if let status = statusForCellularState(restrictedState) {
                                subscriber.putNext(status)
                            }
                        }
                    } else {
                        subscriber.putNext(.allowed)
                        subscriber.putCompletion()
                    }
                    return EmptyDisposable
            }
            case .siri:
                if let siriAuthorization = siriAuthorization {
                    return Signal { subscriber in
                        let status = siriAuthorization()
                        subscriber.putNext(status)
                        subscriber.putCompletion()
                        return EmptyDisposable
                    }
                    |> then(self.siri
                        |> mapToSignal { authorized -> Signal<AccessType, NoError> in
                            if let authorized = authorized {
                                return .single(authorized ? .allowed : .denied)
                            } else {
                                return .complete()
                            }
                        })
                } else {
                    return .single(.denied)
                }
            case .location:
                return Signal { subscriber in
                    let status = CLLocationManager.authorizationStatus()
                    switch status {
                        case .authorizedAlways, .authorizedWhenInUse:
                            subscriber.putNext(.allowed)
                        case .denied, .restricted:
                            subscriber.putNext(.denied)
                        case .notDetermined:
                            subscriber.putNext(.notDetermined)
                        @unknown default:
                            fatalError()
                    }
                    subscriber.putCompletion()
                    return EmptyDisposable
                }
                |> then(self.location
                    |> mapToSignal { authorized -> Signal<AccessType, NoError> in
                        if let authorized = authorized {
                            return .single(authorized ? .allowed : .denied)
                        } else {
                            return .complete()
                        }
                    }
                )
            case .camera:
                return Signal { subscriber in
                    let status = AVCaptureDevice.authorizationStatus(for: .video)
                    switch status {
                    case .authorized:
                        subscriber.putNext(.allowed)
                    case .denied, .restricted:
                        subscriber.putNext(.denied)
                    case .notDetermined:
                        subscriber.putNext(.notDetermined)
                    @unknown default:
                        fatalError()
                    }
                    subscriber.putCompletion()
                    return EmptyDisposable
                }
                |> then(self.camera
                    |> mapToSignal { authorized -> Signal<AccessType, NoError> in
                        if let authorized = authorized {
                            return .single(authorized ? .allowed : .denied)
                        } else {
                            return .complete()
                        }
                    }
                )
            case .microphone:
                return Signal { subscriber in
                    let status = AVCaptureDevice.authorizationStatus(for: .audio)
                    switch status {
                    case .authorized:
                        subscriber.putNext(.allowed)
                    case .denied, .restricted:
                        subscriber.putNext(.denied)
                    case .notDetermined:
                        subscriber.putNext(.notDetermined)
                    @unknown default:
                        fatalError()
                    }
                    subscriber.putCompletion()
                    return EmptyDisposable
                }
                |> then(self.microphone
                    |> mapToSignal { authorized -> Signal<AccessType, NoError> in
                        if let authorized = authorized {
                            return .single(authorized ? .allowed : .denied)
                        } else {
                            return .complete()
                        }
                    }
                )
            default:
                return .single(.notDetermined)
        }
    }
    
    public static func authorizeAccess(
        to subject: DeviceAccessSubject,
        onlyCheck: Bool = false,
        registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil,
        requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil,
        locationManager: LocationManager? = nil,
        presentationData: PresentationData? = nil,
        present: @escaping (ViewController, Any?) -> Void = { _, _ in },
        openSettings: @escaping () -> Void = { },
        displayNotificationFromBackground: @escaping (String) -> Void = { _ in },
        _ completion: @escaping (Bool) -> Void = { _ in }) {
            switch subject {
                case let .camera(cameraSubject):
                    let status = AVCaptureDevice.authorizationStatus(for: .video)
                    if case .notDetermined = status {
                        if !onlyCheck {
                            AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
                                Queue.mainQueue().async {
                                    completion(response)
                                    self.cameraPromise.set(.single(response))
                                    if !response, let presentationData = presentationData {
                                        let text: String
                                        switch cameraSubject {
                                            case .video:
                                                text = presentationData.strings.AccessDenied_Camera
                                            case .videoCall:
                                                text = presentationData.strings.AccessDenied_VideoCallCamera
                                            case .qrCode:
                                                text = presentationData.strings.AccessDenied_QrCamera
                                            case .ageVerification:
                                                text = presentationData.strings.AccessDenied_AgeVerificationCamera
                                        }
                                        present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                            openSettings()
                                        })]), nil)
                                    }
                                }
                            }
                        } else {
                            completion(true)
                        }
                    } else if [.restricted, .denied].contains(status) {
                        completion(false)
                        if let presentationData = presentationData {
                            let text: String
                            if case .restricted = status {
                                text = presentationData.strings.AccessDenied_CameraRestricted
                            } else {
                                switch cameraSubject {
                                    case .video:
                                        text = presentationData.strings.AccessDenied_Camera
                                    case .videoCall:
                                        text = presentationData.strings.AccessDenied_VideoCallCamera
                                    case .qrCode:
                                        text = presentationData.strings.AccessDenied_QrCamera
                                    case .ageVerification:
                                        text = presentationData.strings.AccessDenied_AgeVerificationCamera
                                }
                            }
                            present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                openSettings()
                            })]), nil)
                        }
                    } else if case .authorized = status {
                        completion(true)
                    } else {
                        assertionFailure()
                        completion(true)
                    }
                case let .microphone(microphoneSubject):
                    if AVAudioSession.sharedInstance().recordPermission == .granted {
                        completion(true)
                    } else {
                        AVAudioSession.sharedInstance().requestRecordPermission({ granted in
                            Queue.mainQueue().async {
                                if granted {
                                    completion(true)
                                } else if let presentationData = presentationData {
                                    completion(false)
                                    let text: String
                                    switch microphoneSubject {
                                        case .audio:
                                            text = presentationData.strings.AccessDenied_VoiceMicrophone
                                        case .video:
                                            text = presentationData.strings.AccessDenied_VideoMicrophone
                                        case .voiceCall:
                                            text = presentationData.strings.AccessDenied_CallMicrophone
                                    }
                                    present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                        openSettings()
                                    })]), nil)
                                    if case .voiceCall = microphoneSubject {
                                        displayNotificationFromBackground(text)
                                    }
                                }
                                self.microphonePromise.set(.single(granted))
                            }
                        })
                    }
                case let .mediaLibrary(mediaLibrarySubject):
                    let continueWithValue: (Bool) -> Void = { value in
                        Queue.mainQueue().async {
                            if value {
                                completion(true)
                            } else if let presentationData = presentationData {
                                completion(false)
                                let text: String
                                switch mediaLibrarySubject {
                                    case .send:
                                        text = presentationData.strings.AccessDenied_PhotosAndVideos
                                    case .save:
                                        text = presentationData.strings.AccessDenied_SaveMedia
                                    case .wallpaper:
                                        text = presentationData.strings.AccessDenied_Wallpapers
                                    case .qrCode:
                                        text = presentationData.strings.AccessDenied_QrCode
                                }
                                present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                    openSettings()
                                })]), nil)
                            }
                        }
                    }
                    if let value = cachedMediaLibraryAccessStatus.with({ $0 }) {
                        continueWithValue(value)
                    } else {
                        PHPhotoLibrary.requestAuthorization({ status in
                            let value: Bool
                            switch status {
                                case .restricted, .denied, .notDetermined:
                                    value = false
                                case .authorized, .limited:
                                    value = true
                                @unknown default:
                                    fatalError()
                            }
                            let _ = cachedMediaLibraryAccessStatus.swap(value)
                            continueWithValue(value)
                        })
                    }
                case let .location(locationSubject):
                    let status = CLLocationManager.authorizationStatus()
                    let hasPreciseLocation: Bool
                    if #available(iOS 14.0, *) {
                        if case .fullAccuracy = CLLocationManager().accuracyAuthorization {
                            hasPreciseLocation = true
                        } else {
                            hasPreciseLocation = false
                        }
                    } else {
                        hasPreciseLocation = true
                    }
                    switch status {
                        case .authorizedAlways:
                            if case .live = locationSubject, !hasPreciseLocation {
                                completion(false)
                                if let presentationData = presentationData {
                                    let text = presentationData.strings.AccessDenied_LocationPreciseDenied
                                    present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                        openSettings()
                                    })]), nil)
                                }
                            } else {
                                completion(true)
                            }
                        case .authorizedWhenInUse:
                            switch locationSubject {
                                case .send, .tracking, .weather:
                                    completion(true)
                                case .live:
                                    completion(false)
                                    if let presentationData = presentationData {
                                        let text = presentationData.strings.AccessDenied_LocationAlwaysDenied
                                        present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                            openSettings()
                                        })]), nil)
                                    }
                            }
                        case .denied, .restricted:
                            completion(false)
                            if let presentationData = presentationData {
                                let text: String
                                if status == .denied {
                                    switch locationSubject {
                                        case .send, .live:
                                            text = presentationData.strings.AccessDenied_LocationDenied
                                        case .tracking:
                                            text = presentationData.strings.AccessDenied_LocationTracking
                                        case .weather:
                                            text = presentationData.strings.AccessDenied_LocationWeather
                                    }
                                } else {
                                    text = presentationData.strings.AccessDenied_LocationDisabled
                                }
                                present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                                    openSettings()
                                })]), nil)
                            }
                        case .notDetermined:
                            switch locationSubject {
                                case .send, .tracking, .weather:
                                    locationManager?.requestWhenInUseAuthorization(completion: { status in
                                        completion(status == .authorizedWhenInUse || status == .authorizedAlways)
                                    })
                                case .live:
                                    locationManager?.requestAlwaysAuthorization(completion: { status in
                                        completion(status == .authorizedAlways)
                                    })
                            }
                        @unknown default:
                            fatalError()
                }
                case .contacts:
                    let _ = (self.contactsPromise.get()
                    |> take(1)
                    |> deliverOnMainQueue).start(next: { value in
                        if let value = value {
                            completion(value)
                        } else {
                            switch CNContactStore.authorizationStatus(for: .contacts) {
                                case .notDetermined:
                                    let store = CNContactStore()
                                    store.requestAccess(for: .contacts, completionHandler: { authorized, _ in
                                        self.contactsPromise.set(.single(authorized))
                                        completion(authorized)
                                    })
                                case .authorized:
                                    self.contactsPromise.set(.single(true))
                                    completion(true)
                                case .limited:
                                    self.contactsPromise.set(.single(true))
                                    completion(true)
                                default:
                                    self.contactsPromise.set(.single(false))
                                    completion(false)
                            }
                        }
                    })
                case .notifications:
                    if let registerForNotifications = registerForNotifications {
                        registerForNotifications { result in
                            self.notificationsPromise.set(.single(result))
                            completion(result)
                        }
                    }
                case .siri:
                    if let requestSiriAuthorization = requestSiriAuthorization {
                        requestSiriAuthorization { result in
                            self.siriPromise.set(.single(result))
                            completion(result)
                        }
                    }
                case .cellularData:
                    if let presentationData = presentationData {
                        present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.Permissions_CellularDataTitle_v0, text: presentationData.strings.Permissions_CellularDataText_v0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
                            openSettings()
                        })]), nil)
                    }
            }
    }
}
